package com.adwyxx.generator.utils;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.baomidou.mybatisplus.generator.fill.Property;
import com.baomidou.mybatisplus.generator.keywords.MySqlKeyWordsHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 代码生成工具类
 * 详细配置参见官网：https://baomidou.com/reference/new-code-generator-configuration/
 *
 * @author Leo
 * @date 2024/5/29 11:36
 */
@Slf4j
@Component
public class CodeGeneratorUtil {
    /**
     * 生成代码的输出目录，默认为 D:/output
     */
    @Value("${code-generator.output-dir:'D:/output'}")
    private String outputDir;

    /**
     * 是否将生成的entity,service,controller,mapper,xml文件输出到同一个文件夹中，而不是分包分目录输出
     */
    @Value("${code-generator.use-single-output-dir}")
    private boolean useSingleOutputDir;

    /**
     * 作者
     */
    @Value("${code-generator.author}")
    private String author;

    /**
     * 表前缀过滤
     */
    @Value("${code-generator.table-prefix}")
    private String tablePrefix;

    /***
     * 生成数据源配置
     * @param url 数据库链接URL
     * @param username 用户名
     * @param password 用户密码
     * @param schema 表空间
     * @return DataSourceConfig
     */
    public DataSourceConfig buildDataSourceConfig(String url, String username, String password, String schema) {
        return new DataSourceConfig
                .Builder(url, username, password)
                .schema(schema)
                // 使用元数据查询的方式生成代码,默认已经根据jdbcType来适配java类型,支持使用typeConvertHandler来转换需要映射的类型映射
                .keyWordsHandler(new MySqlKeyWordsHandler())
                .build();
    }

    /**
     * 生成全局配置
     *
     * @return GlobalConfig
     */
    public GlobalConfig buildGlobalConfig() {
        return new GlobalConfig.Builder()
                .outputDir(this.outputDir) // 设置输出目录
                .author(this.author) // 设置作者名
                .enableSwagger() // 开启 Swagger 模式
                .dateType(DateType.ONLY_DATE) // 设置时间类型策略
                .commentDate("yyyy-MM-dd") // 设置注释日期格式
                .build();
    }

    /**
     * 获取包配置，用于定义生成代码的包结构，包括父包名、模块名、实体类包名、服务层包名等
     *
     * @param packageName       报名，如 com.xxx
     * @param moduleName        模块名
     * @param entityPackage     实体包名，如 dto
     * @param servicePackage    Service包名，如 service
     * @param mapperPackage     Mapper接口包名
     * @param controllerPackage controller 包名
     * @return PackageConfig
     */
    public PackageConfig buildPackageConfig(String packageName, String moduleName, String entityPackage, String servicePackage, String mapperPackage, String controllerPackage) {
        // String projectPath = System.getProperty("user.dir");
        // String path = projectPath + "\\src\\main\\java";
        // String mapperXMLPath = path+"\\src\\main\\resources\\mapper";
        String path = this.outputDir + "/" + moduleName;
        Map<OutputFile, String> pathInfo = new HashMap<>();
        if(this.useSingleOutputDir){
            // entity,service,controller,mapper,xml 采用统一的输出地址，不再分包分目录
            Arrays.asList(OutputFile.values()).forEach(key -> pathInfo.put(key,path));
        }
        else{
            pathInfo.put(OutputFile.xml,path+"/xml");
        }
        return new PackageConfig.Builder()
                .parent(packageName) // 设置父包名 cn.crccre
                .moduleName(moduleName) // 设置父包模块名 myModule
                .entity(entityPackage) // 设置 Entity 包名
                .service(servicePackage) // 设置 Service 包名
                .serviceImpl(servicePackage + ".impl") // 设置 Service Impl 包名
                .mapper(mapperPackage) // 设置 Mapper 包名
                .xml(mapperPackage) // 设置 Mapper XML 包名
                .controller(controllerPackage) // 设置 Controller 包名
                .pathInfo(pathInfo) // 设置路径配置信息
                .build();
    }

    /**
     * 获取包配置，用于定义生成代码的包结构，包括父包名、模块名、实体类包名、服务层包名等
     *
     * @param packageName 报名，如 com.xxx
     * @param moduleName  模块名
     * @return PackageConfig
     * @apiNote 默认值设置：
     * entityPackage 实体包名，如 dto
     * servicePackage Service包名，如 service
     * mapperPackage Mapper接口包名
     * controllerPackage controller 包名
     */
    public PackageConfig buildPackageConfig(String packageName, String moduleName) {
        return buildPackageConfig(packageName, moduleName, "dto", "service", "mapper", "controller");
    }

    /**
     * 策略配置:MyBatis-Plus 代码生成器的核心部分,定制代码生成的规则
     *
     * @return StrategyConfig
     */
    public StrategyConfig buildStrategyConfig(ArrayList<String> tables) {
        /*
         * 注意：
         * 1.自定义文件命名，注意 %s 会自动填充表实体属性(各层文件名称方式，例如：%sDto生成UserDto, %s为占位符)
         * 2.指定自定义模板路径，注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
         */
        return new StrategyConfig.Builder()
                /**--- 添加要生成代码的数据库表 ---**/
                .addInclude(tables)
                .addTablePrefix(this.tablePrefix) // 表前缀过滤
                /**--- 定制 Entity 生成规则 ---**/
                .entityBuilder()
                //.idType(IdType.ASSIGN_UUID)  // 主键ID生成规则
                //.superClass(BaseDto.class) // 设置父类
                .enableLombok() // 开启Lombok模式
                .enableChainModel() // 开启链式模式
                .formatFileName("%sDto")
                .columnNaming(NamingStrategy.underline_to_camel) // 数据库表字段映射到实体的命名策略
                .logicDeleteColumnName("DEL_FLAG")       // 逻辑删除字段
                .versionColumnName("VERSION")           // 乐观锁
                .addTableFills(new Column("CREATE_TIME", FieldFill.INSERT))      // 新增时填充
                .addTableFills(new Column("LAST_UPDATE_TIME", FieldFill.INSERT_UPDATE)) // 新增或修改时填充
                .addSuperEntityColumns("id","CREATE_TIME", "LAST_UPDATE_TIME") // 添加父类公共字段
                //.addIgnoreColumns("pt") // 添加忽略字段
                .addTableFills(new Column("CREATE_TIME", FieldFill.INSERT)) // 添加表自动填充的字段
                .addTableFills(new Property("LAST_UPDATE_TIME", FieldFill.INSERT_UPDATE)) //添加表自动填充的字段
                .javaTemplate("/templates/dto.java") // 设置实体类模板
                .enableFileOverride()
                /**--- 定制 Service 生成规则 ---**/
                .serviceBuilder()
                .serviceTemplate("/templates/service.java") // 设置 Service 模板
                .serviceImplTemplate("/templates/serviceImpl.java") // 设置 ServiceImpl 模板
                .formatServiceFileName("%sService")
                .formatServiceImplFileName("%sServiceImpl")
                /**--- 定制 Mapper 生成规则 ---**/
                .mapperBuilder()
                .superClass(BaseMapper.class)
                .enableBaseResultMap() // 启用 BaseResultMap 基类
                .enableFileOverride()
                .mapperTemplate("/templates/mapper.java")
                .mapperXmlTemplate("/templates/mapper.xml")
                .formatMapperFileName("%sMapper")
                .formatXmlFileName("%sMapper")
                /**--- 定制 Controller 生成规则 ---**/
                .controllerBuilder()
                //.superClass(BaseController.class) // 设置父类
                .enableFileOverride() // 覆盖已生成文件
                .enableHyphenStyle()  // 开启驼峰转连字符
                .enableRestStyle() // 开启生成@RestController 控制器
                .template("/templates/controller.java")
                .formatFileName("%sController") // 格式话文件名
                .build();
    }

    /**
     * 生成代码
     *
     * @param url         database Url
     * @param username    database username
     * @param password    database password
     * @param schema      database schema
     * @param tables      tables to be generate
     * @param packageName generate to package name
     * @param moduleName  generate to module name
     */
    public void generate(String url, String username, String password, String schema, ArrayList<String> tables, String packageName, String moduleName) {
        AutoGenerator generator = new AutoGenerator(this.buildDataSourceConfig(url, username, password, schema));
        generator.global(this.buildGlobalConfig());
        generator.strategy(this.buildStrategyConfig(tables));
        generator.packageInfo(this.buildPackageConfig(packageName, moduleName));
        generator.execute();
    }

    /**
     * 快速生成
     *
     * @param url      数据库url,无法读取表注释，解决方法：MySQL链接增加属性 remarks=true&useInformationSchema=true
     * @param username 用户名
     * @param password 密码
     * @param tables   需要生成代码的数据库表
     */
    public void autoGenerate(String url, String username, String password, ArrayList<String> tables, String outputDir) {
        //if (!StringUtils.hasLength(outputDir)) {
        //    outputDir = System.getProperty("user.dir");
        //}
        //String projectPath = outputDir + "\\src\\main\\java";
        //String mapperXMLPath = outputDir + "\\src\\main\\resources\\mapper";
        FastAutoGenerator.create(url, username, password)
                .globalConfig(builder -> {
                    builder.author(this.author) // 设置author
                            .enableSwagger()    // 添加Swagger注解
                            .outputDir(this.outputDir);  //设置路径
                })
                .packageConfig(builder -> {
                    builder.parent("com.adwyxx.account")    // 包路径
                            .entity("dto")                  // 实体包名
                            .service("service")             // service包名
                            .serviceImpl("service.impl")    // service实现类包名
                            .mapper("mapper")               // dao层包名
                            .xml("mapper")                  // dao XML包名
                            .controller("controller")       // controller包名
                            .pathInfo(Collections.singletonMap(OutputFile.xml, this.outputDir));    // xml映射文件包名
                })
                .strategyConfig(builder -> {
                    builder.addInclude(tables)
                            .addTablePrefix(this.tablePrefix)   // 表前缀
                            .serviceBuilder()                   // 构建service
                            .formatServiceFileName("%sService") // service命名规则
                            .formatServiceImplFileName("%sServiceImpl") //service实现类命名规则
                            .controllerBuilder()        // 构建controller
                            .formatFileName("%sController") // controller命名规则
                            .enableRestStyle() //驼峰命名
                            .entityBuilder()    //构建实体类
                            .enableChainModel()
                            .idType(IdType.AUTO)    //id类型
                            .enableLombok()         //lombok
                            .naming(NamingStrategy.underline_to_camel)  // 数据库下划线转为驼峰式命名
                            .logicDeleteColumnName("del_flag")       // 逻辑删除字段
                            .versionColumnName("VERSION")           // 乐观锁
                            .addTableFills(new Column("CREATE_TIME", FieldFill.INSERT))      // 新增时填充
                            .addTableFills(new Column("LAST_UPDATE_TIME", FieldFill.INSERT_UPDATE)) // 新增或修改时填充
                            .enableTableFieldAnnotation()
                            .mapperBuilder()    // 构建dao层
                            .enableBaseResultMap() // 通用map
                            .superClass(BaseMapper.class)   // 继承 BaseMapper
                            .formatMapperFileName("%sMapper")   // dao层命名规则
                            .formatXmlFileName("%sMapper")  // xml映射文件命名规则
                            .mapperAnnotation(Mapper.class); // dao层添加@Mapper注解
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker模板引擎
                .execute();
    }
}
